﻿using System;
using System.Collections.Generic;
using Space2D_Utils;

namespace Space2D_Core
{
	public static class WorldManager
	{
		public static Action<PhysicalObject> EventRegistered;
		public static Action<PhysicalObject> EventDeRegistered;

		static WorldManager()
		{
			;
		}

		public static void Init(double G, double gravityDelimiter, Vector2d worldR)
		{
			sm_G = G;
			sm_gravityDelimiter = gravityDelimiter;
			sm_worldR = worldR;

			sm_grid = new Grid2D(new AABB(new Vector2d(0, 0), sm_worldR), 10, 10);

			sm_gravityObjects.Clear();
			sm_objects.Clear();
		}

		public static void DeInit()
		{
			sm_gravityObjects.Clear();
			sm_objects.Clear();
		}

		public static void Register(PhysicalObject obj)
		{
			if (obj.Mass > sm_gravityDelimiter)
			{
				Logger.Log("Register " + obj.Name + " as gravity object.");
				sm_gravityObjects.Add(obj);
			}
			else
			{
				Logger.Log("Register " + obj.Name + " as non gravity object.");
				sm_objects.Add(obj);
			}
		}

		public static void DeRegister(PhysicalObject obj)
		{
			if (obj.Mass > sm_gravityDelimiter)
			{
				sm_gravityObjects.Remove(obj);
			}
			else
			{
				sm_objects.Remove(obj);
			}

			if (EventDeRegistered != null)
			{
				EventDeRegistered(obj);
			}
		}

		public static void UpdateMinMaxSize()
		{
			for (int i = 0; i < sm_gravityObjects.Count; ++i)
			{
				PhysicalObject obj = sm_gravityObjects[i];
				
				if (obj.R.X > sm_maxR.X)
				{
					if (obj.R.Y > sm_maxR.Y)
					{
						sm_maxR = new Vector2d(obj.R.X, obj.R.Y);
					}
					else
					{
						sm_maxR = new Vector2d(obj.R.X, sm_maxR.Y);
					}
				}
				else if (obj.R.Y > sm_maxR.Y)
				{
					sm_maxR = new Vector2d(sm_maxR.X, obj.R.Y);
				}

				// max size
				if (obj.R.X > sm_maxR.X)
				{
					if (obj.R.Y > sm_maxR.Y)
					{
						sm_maxR = new Vector2d(obj.R.X, obj.R.Y);
					}
					else
					{
						sm_maxR = new Vector2d(obj.R.X, sm_maxR.Y);
					}
				}
				else if (obj.R.Y > sm_maxR.Y)
				{
					sm_maxR = new Vector2d(sm_maxR.X, obj.R.Y);
				}

				// min size
				if (obj.R.X < sm_minR.X)
				{
					if (obj.R.Y < sm_minR.Y)
					{
						sm_minR = new Vector2d(obj.R.X, obj.R.Y);
					}
					else
					{
						sm_minR = new Vector2d(obj.R.X, sm_minR.Y);
					}
				}
				else if (obj.R.Y < sm_minR.Y)
				{
					sm_minR = new Vector2d(sm_minR.X, obj.R.Y);
				}

				if (EventRegistered != null)
				{
					EventRegistered(obj);
				}
			}

			for (int i = 0; i < sm_objects.Count; ++i)
			{
				PhysicalObject obj = sm_objects[i];

				if (obj.R.X > sm_maxR.X)
				{
					if (obj.R.Y > sm_maxR.Y)
					{
						sm_maxR = new Vector2d(obj.R.X, obj.R.Y);
					}
					else
					{
						sm_maxR = new Vector2d(obj.R.X, sm_maxR.Y);
					}
				}
				else if (obj.R.Y > sm_maxR.Y)
				{
					sm_maxR = new Vector2d(sm_maxR.X, obj.R.Y);
				}

				// max size
				if (obj.R.X > sm_maxR.X)
				{
					if (obj.R.Y > sm_maxR.Y)
					{
						sm_maxR = new Vector2d(obj.R.X, obj.R.Y);
					}
					else
					{
						sm_maxR = new Vector2d(obj.R.X, sm_maxR.Y);
					}
				}
				else if (obj.R.Y > sm_maxR.Y)
				{
					sm_maxR = new Vector2d(sm_maxR.X, obj.R.Y);
				}

				// min size
				if (obj.R.X < sm_minR.X)
				{
					if (obj.R.Y < sm_minR.Y)
					{
						sm_minR = new Vector2d(obj.R.X, obj.R.Y);
					}
					else
					{
						sm_minR = new Vector2d(obj.R.X, sm_minR.Y);
					}
				}
				else if (obj.R.Y < sm_minR.Y)
				{
					sm_minR = new Vector2d(sm_minR.X, obj.R.Y);
				}

				if (EventRegistered != null)
				{
					EventRegistered(obj);
				}
			}
		}

		public static Vector2d CalcGravity(Vector2d pos, PhysicalObject notInclude = null)
		{
			Vector2d gravity = new Vector2d(0, 0);
			for (int i = 0; i < sm_gravityObjects.Count; ++i)
			{
				PhysicalObject obj = sm_gravityObjects[i];

				if (notInclude != obj)
					{
						Vector2d distance = pos - obj.Position;
						double a = sm_G * obj.Mass / distance.SqrLength;

						if (a > 0.0001)
						{
							gravity -= (distance.Normalized() * a);
						}
					}
				}

			return gravity;
		}

		public static void Tick(int ms)
		{
			UpdateAcceleration();

			for (int i = 0; i < sm_gravityObjects.Count; ++i)
			{
				PhysicalObject obj = sm_gravityObjects[i];
				obj.Tick(ms);

				sm_grid.Add(obj);
			}

			for (int i = 0; i < sm_objects.Count; ++i)
			{
				PhysicalObject obj = sm_objects[i];
				obj.Tick(ms);

				sm_grid.Add(obj);
			}
		}

		public static int GetWorldObjectsCount()
		{
			return sm_gravityObjects.Count + sm_objects.Count;
		}

		private static void UpdateAcceleration()
		{
			for (int i = 0; i < sm_gravityObjects.Count; ++i)
			{
				PhysicalObject obj = sm_gravityObjects[i];
				obj.SetAcceleration(new Vector2d(0, 0));
			}

			for (int i = 0; i < sm_gravityObjects.Count; ++i)
			{
				PhysicalObject obj = sm_gravityObjects[i];
				obj.AddAcceleration(CalcGravity(obj.Position, obj));
			}

			for (int i = 0; i < sm_objects.Count; ++i)
			{
				PhysicalObject obj = sm_objects[i];
				obj.SetAcceleration(new Vector2d(0, 0));
			}

			for (int i = 0; i < sm_objects.Count; ++i)
			{
				PhysicalObject obj = sm_objects[i];
				obj.AddAcceleration(CalcGravity(obj.Position));
			}
		}

		private static List<PhysicalObject> sm_objects = new List<PhysicalObject>();
		private static List<PhysicalObject> sm_gravityObjects = new List<PhysicalObject>();
		private static double sm_G = 6.67545E-11;
		private static double sm_gravityDelimiter = 1.0E+6;
		private static Vector2d sm_worldR = new Vector2d(0, 0);

		private static Vector2d sm_maxR = new Vector2d(0, 0);
		private static Vector2d sm_minR = new Vector2d(double.MaxValue, double.MaxValue);
		private static Grid2D sm_grid = null;
	}
}
